<?php

/**
 * Простая форма a-la ActiveRecord. Автоматически строит запросы для
 * загрузки/сохранения/удаления элемента.
 *
 */
abstract class Kernel_Action_Form_Simple extends Kernel_Action_Form 
{

    /**
     * Основная таблица, в которой хранится редактируемый элемент.
     *
     * @var string
     */
    protected $_table;

    /**
     * Элементы основной таблицы, которые нужно включить в форму.
     *
     * @var array
     */
    protected $_include = array();

    /**
     * Элементы основной таблицы, которые нужно исключить из формы.
     *
     * @var array
     */
    protected $_exclude = array();

    /**
     * Наименования полей, которые нужно сохранить в основную таблицу -
     * используется для отсеивания полей, добавленных вручную при формировании
     * запросов на запись данных.
     *
     * @var array
     */
    private $_table_fields = array();

    /**
     * Проекция кодов исключения БД на сообщения об ошибке сохранения формы.
     *
     * @var array
     */
    private $_map = array();

    /**
     * Реализация обработки пользовательского запроса.
     *
     */
    protected function _process() 
    {
        if (is_null($this->_table)) {
            trigger_error('Form table is not specified', E_USER_ERROR);
        }
        parent::_process();
    }

    /**
     * Инициализирует элементы формы.
     *
     */
    protected function _initElements() 
    {
        if (!empty($this->_include) && !empty($this->_exclude)) {
            trigger_error('Please specify either $_include or $_exclude, but not both', E_USER_ERROR);
        } else {
            $this->_table_fields = !empty($this->_include)
                ? $this->_fromTable($this->_table, $this->_include, self::INCLUSIVE)
                : $this->_fromTable($this->_table, $this->_exclude, self::EXCLUSIVE);
        }
    }

    /**
     * Загружает данные формы.
     *
     * @return  array
     */
    protected function _load() 
    {
        $fields = array();
        foreach ($this->_table_fields as $field) {
            if ('file' != $this->_getElementParam($field, 'type')) {
                $fields[] = '`' . $field . '`';
            }
        }
        $query = 'SELECT
                         ' . implode(', ', $fields) . '
                    FROM
                         `' . $this->_table . '`
                   WHERE
                         `' . $this->_id_field . '` = "' . $this->id . '"';
        return database::fetchRow($query);
    }

    /**
     * Сохраняет данные формы.
     *
     * @throws  Kernel_Form_Exception_Save
     */
    protected function _save() 
    {
        try {
            parent::_save();
        } catch (Kernel_Db_Exception $e) {
            throw new Kernel_Form_Exception_Save($this->_message($e));
        }
    }

    /**
     * Добавляет новую запись.
     *
     */
    protected function _insert() 
    {
        return $this->id = database::insert($this->_table, $this->_data());
    }

    /**
     * Обновляет существующую запись.
     *
     */
    protected function _update() 
    {
        database::update($this->_table, $this->_data(), array($this->_id_field => $this->id));
    }

    /**
     * Добавляет сообщение об ошибке сохранения формы для указанного кода.
     *
     * @param   integer $code
     * @param   string  $message
     */
    protected function _map($code, $message = null) 
    {
        foreach (is_array($code) ? $code : array($code => $message) as $code => $message) {
            $this->_map[$code] = $message;
        }
        return $this;
    }

    /**
     * Формирует сообщение об ошибке сохранения формы на основе указанного
     * исключения.
     *
     * @param   Kernel_Db_Exception $e
     * @return  string
     */
    protected function _message(Kernel_Db_Exception $e)
     {
        $code = $e->getCode();
        return isset($this->_map[$code]) ? $this->_map[$code] : $e->getMessage();
    }

    /**
     * Возвращает данные для сохранения — только те, которые нужно сохранить в
     * основной таблице.
     *
     * @return  array
     */
    protected function _data() 
    {

        return array_extract(parent::_data(),

            // пропускаем незагруженные файлы — это разница
            array_diff($this->_table_fields, array_diff(

                // наименования всех полей-файлов
                array_keys($this->_getElementsByFilter(array('type' => 'file'))),

                // и наименования полей успешно загруженных файлов
                array_keys($this->_getFiles()))));
    }
    
}
